from google.appengine.ext import webapp, db
from google.appengine.api import users, mail
from google.appengine.ext.webapp import template


import os, urllib, functools, sys, logging, traceback, re

def httpargs(func):
  import inspect, logging
  def wrapper(self):
    args, _, _, defaults = inspect.getargspec(func)
    nondefaults = len(args)-(0 if defaults is None else len(defaults)) # number of arguments without default values
    srg = self.request.get
    srga = self.request.get_all
    
    # typecast without an exception if it doesn't succeed
    def safecast(thetype, thevalue):
      try:
        if thetype is bool and thevalue == 'False':
          return False
        return thetype(thevalue)
      except:
        return thevalue
    
    # bind the arguments, using defaults if not available
    # cast the object to the type of the default (ValueError if it doesn't match)
    # if the default is a list, use get_all instead of get (does not attempt to cast values inside the list)
    
    arguments = tuple(srg(name) for name in args[1:nondefaults]) + \
      tuple( (srga(name) if isinstance(defaults[i], list) else safecast(type(defaults[i]), srg(name))) if srg(name) != '' else defaults[i]
          for i, name in enumerate(args[nondefaults:]) )

    # if it is a method call that is decorated, it has an im_self attribute
    if not hasattr(func, 'im_self') or func.im_self is None:
      return func(self, *arguments)
    else:
      return func(*arguments)

  return wrapper

  
class BaseRequestHandler(webapp.RequestHandler):

  def __init__(self,*args,**kwargs):
    webapp.RequestHandler.__init__(self,*args,**kwargs)
    self.original_get = self.get
    self.get = functools.partial(self.handler, self.get)
    self.post = functools.partial(self.handler, self.post)
    self.head = self.get

  # A replacement for http get/post handlers
  def handler(self, method, *args, **kwargs):
    try:
      self.user = users.get_current_user()
      if self.user is not None or isinstance(self,TaskRequestHandler):
        httpargs(method)(self)
      else:
        self.seeother(users.create_login_url(self.request.uri))
    except db.Timeout,e:
      retries = int(self.request.get('_r',1))
      logging.debug('retries: '+str(retries))
      if method.__name__ == 'get' and retries < 5:
        url = re.sub('_r=(\d)',lambda m: '_r='+str(int(m.group(1))+1),self.request.url)
        if not re.search('_r=(\d)',url):
          if '?' not in url:
            url += '?_r=2'
          else:
            url += '&_r=2'
        self.seeother(url)
      else:
        raise e
        
      

  def handle_exception(self, exception, debug_mode): 
    from google.appengine.api import memcache

    exception_name = sys.exc_info()[0].__name__
    exception_details = str(sys.exc_info()[1])
    exception_traceback = ''.join(traceback.format_exception(*sys.exc_info()))
    logging.error(exception_traceback)
    
    if not debug_mode and not isinstance(exception, (SecurityException, db.InternalError,db.Timeout, db.TransactionFailedError)):
      exception_expiration = 3600 # seconds (max 1 mail per hour for a particular exception)
      mail_admin = "ntoshev@gmail.com" # must be admin for the application
      sitename = "siteparse"
      throttle_name = 'exception-'+exception_name
      throttle = memcache.get(throttle_name)
      if throttle is None:
        memcache.add(throttle_name, 1, exception_expiration)
        subject = '[%s] exception [%s: %s]' % (sitename, exception_name, exception_details)
        mail.send_mail_to_admins(sender = mail_admin,
                                 subject = subject,
                                 body = 'Request URL: ' + self.request.url + '\n' +
                                        'Headers: ' + ', '.join('='.join([k,self.request.headers[k]]) for k in self.request.headers) + '\n' +
                                        'Cookies: ' + ', '.join('='.join([k,self.request.cookies[k]]) for k in self.request.cookies) + '\n\n' +
                                        exception_traceback)
                                        
    template_values = {}
    if users.is_current_user_admin() or debug_mode:
      template_values['traceback'] = exception_traceback
    self.response.set_status(500)  
    self.response.out.write(template.render('error.html', template_values))

  def seeother(self, uri):
    self.response.set_status(303)
    import urlparse
    absolute_url = urlparse.urljoin(self.request.uri, uri)
    self.response.headers['Location'] = str(absolute_url)
    self.response.clear()
    
  def set_cookie(self, key, value='', max_age=None, path='/', domain=None, secure=None):
    """
    Set (add) a cookie for the response
    """
    header = key + '=' + urllib.quote(value)
    if max_age:
      import datetime
      header += '; expires=' + (datetime.datetime.now() + datetime.timedelta(seconds=max_age)).strftime("%a, %d %b %Y %H:%M:%S GMT")
    if path != '/':
      header += '; path='+path
    if domain is not None:
      header = '; domain='+domain
    if secure is not None:
      header = '; secure='+str(secure)
    self.response.headers.add_header('Set-Cookie', header)
    
  def clear_cookie(self, key):
    self.response.headers.add_header('Set-Cookie', key +'=; expires=Tue, 01 Jan 2008 00:00:00 GMT')



def keep_in_range(v, minv, maxv):
  return min(max(v, minv), maxv)


# used to distinguish Task Web Hooks from other requests
class TaskRequestHandler(BaseRequestHandler):
  pass
    
  
from exceptions import Exception
class SecurityException(Exception):
  def __init__(self, *args):
    if len(args) > 0:
      self.message = args[0]
    else:
      self.message = ''

  def __str__(self):
    return repr(self.message)



  
  
  
  
  